/**
 * Copyright 2019 吉鼎科技.

 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package cn.easyplatform.web.task.api;

import cn.easyplatform.lang.Strings;
import cn.easyplatform.web.contexts.Contexts;
import cn.easyplatform.web.task.event.EventListenerHandler;
import cn.easyplatform.web.utils.PageUtils;
import org.zkoss.zuti.zul.ForEach;
import org.zkoss.zuti.zul.TemplateBasedShadowElement;
import org.zkoss.xel.VariableResolver;
import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.Execution;
import org.zkoss.zk.ui.Executions;
import org.zkoss.zk.ui.sys.ShadowElementsCtrl;
import org.zkoss.zk.ui.util.ForEachStatus;
import org.zkoss.zk.ui.util.Template;
import org.zkoss.zul.impl.XulElement;

import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.NoSuchElementException;

/**
 * @author <a href="mailto:davidchen@epclouds.com">littleDog</a> <br/>
 * @since 2.0.0 <br/>
 */
public class ForEachRenderer implements org.zkoss.zuti.ForEachRenderer {

    private boolean onCreate;

    public void render(ForEach forEachComp, Component host, Template tm, List<?> list, boolean isUsingListModel) {
        final int begin = forEachComp.getBegin();
        final int end = forEachComp.getEnd();
        final int step = forEachComp.getStep();
        onCreate = true;
        render(forEachComp, host, tm, begin, end, step, list, isUsingListModel);
    }

    public void render(ForEach forEachComp, Component host, Template tm, int begin, int end, int step, List<?> list,
                       boolean isUsingListModel) {
        Component insertBefore = null;
        Object shadowInfo = ShadowElementsCtrl.getCurrentInfo();
        String forEachRenderedCompAttr = TemplateBasedShadowElement.FOREACH_RENDERED_COMPONENTS + forEachComp.getUuid();
        try {
            ShadowElementsCtrl.setCurrentInfo(forEachComp);
            List<Component[]> feCompList = (List<Component[]>) host.getAttribute(forEachRenderedCompAttr);

            if (feCompList != null && feCompList.size() > begin) {
                insertBefore = feCompList.get(begin)[0];
            } else {
                insertBefore = forEachComp.getNextInsertionComponentIfAny();
            }
            int insertIndex = begin;
            for (final ForEachIterator<Object> it = new ForEachIterator<Object>(begin, end, step, list); it
                    .hasNext(); ) {
                VariableResolver variableResolver = initVariableResolver(forEachComp, it);
                final Execution exec = Executions.getCurrent();

                exec.addVariableResolver(variableResolver);

                Component[] creates = null;

                forEachComp.setAttribute(forEachComp.getVar(), variableResolver.resolveVariable(forEachComp.getVar()));
                forEachComp.setAttribute(forEachComp.getVarStatus(),
                        variableResolver.resolveVariable(forEachComp.getVarStatus()));
                creates = tm.create(host, insertBefore, variableResolver, null);
                forEachComp.removeAttribute(forEachComp.getVar());
                forEachComp.removeAttribute(forEachComp.getVarStatus());

                if (isUsingListModel) {
                    if (feCompList == null)
                        feCompList = new LinkedList<Component[]>();
                    if (feCompList.size() > insertIndex)
                        feCompList.add(insertIndex, creates);
                    else
                        feCompList.add(creates);
                    host.setAttribute(forEachRenderedCompAttr, feCompList);
                }

                for (Component comp : creates) {
                    comp.setAttribute(forEachComp.getVar(), variableResolver.resolveVariable(forEachComp.getVar()));
                    comp.setAttribute(forEachComp.getVarStatus(),
                            variableResolver.resolveVariable(forEachComp.getVarStatus()));
                    if (!onCreate)
                        processEvent(comp);
                }
                insertIndex += step;
                exec.removeVariableResolver(variableResolver);
            }
        } finally {
            ShadowElementsCtrl.setCurrentInfo(shadowInfo);
        }
    }

    private void processEvent(Component comp) {
        if (!Strings.isBlank(comp.getEvent())) {
            EventListenerHandler elh = Contexts.getEventListenerHandler();
            comp.addEventListener(elh.getName(), elh);
        }
        for (Component c : comp.getChildren())
            processEvent(c);
    }

    private VariableResolver initVariableResolver(final ForEach forEachComp, final ForEachIterator<Object> it) {
        final Object item = it.next();
        final int index = it.getIndex();
        return new VariableResolver() {
            private ForEachStatus prev = null;
            private ForEachStatus current = null;

            public Object resolveVariable(String name) {
                if (forEachComp.getVar().equals(name)) {
                    return item;
                } else if (forEachComp.getVarStatus().equals(name)) {
                    if (current != null)
                        prev = current;
                    current = new ForEachStatus() {

                        public ForEachStatus getPrevious() {
                            return prev;
                        }

                        public Object getEach() {
                            return getCurrent();
                        }

                        public int getIndex() {
                            return index;
                        }

                        public Integer getBegin() {
                            return it.getBegin();
                        }

                        public Integer getEnd() {
                            return it.getEnd();
                        }

                        public Object getCurrent() {
                            return item;
                        }

                        public boolean isFirst() {
                            return getIndex() == getBegin();
                        }

                        public boolean isLast() {
                            return getIndex() == getEnd();
                        }

                        public Integer getStep() {
                            return forEachComp.getStep();
                        }

                        public int getCount() {
                            return getIndex() + 1;
                        }
                    };
                    return current;
                } else {
                    return null;
                }
            }
        };
    }

    @SuppressWarnings("unchecked")
    private static class ForEachIterator<E> {
        private final int startIndex;
        private final int endIndex;
        private final int step;
        private int cursor;
        private ListIterator<Object> iter;

        ForEachIterator(int begin, int end, int step, List items) {
            this.startIndex = begin;
            this.endIndex = end;
            this.step = step;
            this.cursor = -1;
            if (items != null) {
                if (items.size() > startIndex)
                    this.iter = items.listIterator(startIndex);
                else
                    this.cursor = endIndex; // force point to the end
            }
        }

        int getBegin() {
            return startIndex;
        }

        int getEnd() {
            return endIndex;
        }

        boolean hasPrevious() {
            return (this.cursor - this.step > this.startIndex);
        }

        Object previous() {
            if (!hasPrevious()) {
                throw new NoSuchElementException();
            }
            Object item = null;
            if (iter != null) {
                int prevStep = step;
                while (prevStep-- > 0)
                    item = iter.previous();
                cursor -= step;
            } else {
                item = cursor;
                cursor -= step;
            }
            return item;
        }

        boolean hasNext() {
            return (cursor == -1 || cursor + step <= endIndex);
        }

        int getIndex() {
            return cursor == -1 ? startIndex : cursor;
        }

        Object next() {
            if (!hasNext()) {
                throw new NoSuchElementException();
            }
            Object item = null;
            if (iter != null) {
                int nextStep = step;
                if (cursor == -1) {
                    cursor = startIndex;
                    item = iter.next();
                } else {
                    while (nextStep-- > 0)
                        item = iter.next();
                    cursor += step;
                }
            } else {
                if (cursor == -1)
                    cursor = startIndex;
                else
                    cursor += step;
                item = cursor;
            }
            return item;
        }
    }
}
